In [6]:
var x = 42;
function globalX() {
return x;
}
function localX() {
var x = 2017;
return x;
}
console.log(globalX());
console.log(localX());
Out[6]:
Functions are the only language constructs in ECMAScript 5 which defines a new variable scope. If you want to create a block with its own variable scope you'll use an IIFE (Immediately-Invoked Function Expression). An IIFE defines a function which creates its own variable scope and invokes it immediately.
In [9]:
function scopedX() {
var x = 42;
(function() {
var x = 2017;
console.log(x);
})();
console.log(x);
}
scopedX();
Out[9]:
In [10]:
var x = 42;
function hoistedX(flag) {
if (flag) {
var x = 2017;
return x;
}
return x;
}
console.log(hoistedX(true));
console.log(hoistedX(false));
Out[10]:
What would you expect as output? The first call returns 2017 which you can expect from the code. But the second call returns undefined
! Why? Shouldn't it return 42?
I mentioned that only functions create new variable scopes in ECMAScript 5. The function hoistedX
creates a new scope, the if
statement does not. Actually, the code above is equivalent to this one:
In [11]:
var x = 42;
function hoistedX(flag) {
var x;
if (flag) {
x = 2017;
return x;
}
return x;
}
console.log(hoistedX(true));
console.log(hoistedX(false));
Out[11]:
The if
statement does not create a new variable scope. Therefore the return
statement outside the if
block returns the same x
variable like the one in the if
block. The only difference is that the if
block initializes the variable before it returns it. Although the x
variable is declared in the if
block it gets "hoisted" to the scope of the enclosing function.
ECMAScript 2015 introduces block scoping. You can use let
to create a variable which is scoped to the block which surrounds it.
In [12]:
var x = 42;
function blockScopedX(flag) {
if (flag) {
let x = 2017;
return x;
}
return x;
}
console.log(blockScopedX(true));
console.log(blockScopedX(false));
Out[12]:
With let
instead of var
the function outputs "2017" and "42" like we would expect.
What happens when you try to access the variable before it is declared?
In [1]:
let z = 42;
if (true) {
console.log(z);
let z = 2017;
console.log(z);
}
console.log(z);
This leads to a ReferenceError
. The variable z
is created as soon as the block is entered. Until the variable is declared (with let
) you cannot access it. You cannot read it and you cannot set it. This is called temporal dead zone.
With var
a variable is created, too, as soon as the block is entered. But it is set to undefined
as long as you don't assign a value to it ("variable hoisting").
You can define constants in ECMAScript 2015 with the const
keyword.
In [3]:
const PI = 3.1415926;
let r = 4.2;
console.log('Perimeter = ' + 2 * PI * r);
Out[3]:
Variables declared with const
cannot be changed; they are immutable. This does not imply that the value which is assigned to the variable cannot be changed.
In [4]:
const obj = {};
obj.prop = 42;
console.log(obj.prop);
Out[4]:
As you can see you can assign new properties to the object which the constant variable obj
points to. You cannot change the reference to that object, but you can change the object itself.
var
versus let
versus const
Which one should you use in your code? I recommend to use either let
or const
.
const
. That should be your default. You cannot change the variable later. This can prevent a whole class of errors.let
if the initial value of the variable can be changed later on.var
. There is no reason to use it anymore.let
and const
. The block surrounding the variable declarations define the scope for variables declared with let
or const
.const
to avoid changing a variable unintentionally. Use let
if you want to declare a variable which changes later on. Avoid var
.
In [1]:
42
Out[1]:
In [3]:
'The answer to the ultimate question of life, the universe, and everything.'
Out[3]:
In [4]:
42 // decimal integer literal
Out[4]:
In [5]:
0xFF // hexadecimal integer literal
Out[5]:
There are two new kinds of integer literals in ECMAScript 2015: binary literals (prefixed with 0b
or 0B
) and octal literals (prefixed with 0o
or 0O
).
In [6]:
0b10011001 // binary literal
Out[6]:
In [7]:
0o42 // octal literal
Out[7]:
In [9]:
function printName(firstName, lastName) {
console.log(firstName + ' ' + lastName);
}
printName('Andreas', 'Schlapsi');
Out[9]:
ECMAScript 2015 introduces template literals which support string interpolation. You write the interpolated expression between ${
and }
. Template literals are delimited by backticks (`).
In [10]:
function printName(firstName, lastName) {
console.log(`${firstName} ${lastName}`);
}
printName('Andreas', 'Schlapsi');
Out[10]:
The interpolated expression can be a more complex JavaScript expression than just a variable reference.
In [12]:
function printSum(a, b) {
console.log(`${a + b}`);
}
printSum(40, 2);
Out[12]:
In [15]:
var divElement =
'<div class="post">\n' +
' <h1>Title</h1>\n' +
'</div>';
console.log(divElement);
Out[15]:
In [16]:
var divElement = '\
<div class="post">\n\
<h1>Title</h1>\n\
</div>';
console.log(divElement);
Out[16]:
The second variant looks a little bit nicer. You need to escape the newlines, though, and it contains more spaces than the first variant.
ECMAScript 2015's template literals are even nicer.
In [1]:
const divElement = `
<div class="post">
<h1>Title</h1>
</div>`;
console.log(divElement);
Out[1]:
It's easy to embed other values with template literals.
In [1]:
const title = 'Awesome page';
const divElement = `
<div class="post">
<h1>${title}</h1>
</div>`;
console.log(divElement);
Out[1]:
In [2]:
console.log('\x41');
Out[2]:
If you want to use a character with a 16-bit code point (first plane or basic multilingual plane - BMP), you can use a unicode escape.
In [3]:
console.log('\u1234');
Out[3]:
Unicode defines other planes (supplementary planes) and to reach that with UTF-16 you can use surrogate pairs. You have to calculate them self.
In [4]:
console.log('\uD83D\uDE00');
Out[4]:
ECMAScript 2015 introduces Unicode code point escapes. You don't have to calculate the surrogate pairs anymore if you use them.
In [6]:
console.log('\u{1F600}');
Out[6]:
In [1]:
const text = '(\u{1F600})';
/\(.\)/.test(text);
Out[1]:
ECMAScript 2015 introduces the u
flag which enables special Unicode handling for regular expressions.
In [1]:
const text = '(\u{1F600})';
/\(.\)/u.test(text);
Out[1]:
This flag enables the special Unicode handling for other patterns, too.
u
flag which enables better support for Unicode characters from supplementary planes.The post "JavaScript has a Unicode problem" (by Mathias Bynens) explains the new Unicode features in ECMAScript 2015 in more details.
Destructuring is a way to extract multiple values from objects or Arrays. You use patterns in locations that receive data like left-hand side of an assignment to specify how the values are extracted.
Suppose we have an object with multiple properties and you want to assign the values of these properties to variables. In ECMAScript 5 you had to assign the properties to the variables separately.
In [1]:
var obj = { a: 1, b: 2, c: 'the answer' };
var value1 = obj.a;
var value2 = obj.b;
var value3 = obj.c;
console.log(value1, value2, value3);
Out[1]:
In ECMAScript 2015 you can use object destructuring to do this in one line.
In [1]:
const obj = { a: 1, b: 2, c: 'the answer' };
const {a: value1, b: value2, c: value3} = obj;
console.log(value1, value2, value3);
Out[1]:
The pattern is written in the form { <property name>: <variable name>, ... }
. In the example above the pattern {a: value1, b: value2, c: value3}
means that the value of property a
is assigned to the variable value1
, the value of property b
is assigned to the variable value2
, and so on.
If the property name and the variable name are the same you can just write the property name.
In [1]:
const obj = { a: 1, b: 2, c: 'the answer' };
// Instead of this:
// let {a: a, b: b, c: c} = obj;
// you can write this:
let {a, b, c} = obj;
console.log(a, b, c);
Out[1]:
In [1]:
const obj = { a: 1, b: 2, c: 'the answer' };
let {a, b, c, d: d = 'is 42'} = obj;
console.log(a, b, c, d);
Out[1]:
Default values work with the short form, too.
In [1]:
const obj = { a: 1, b: 2, c: 'the answer' };
let {a, b, c, d = 'is 42'} = obj;
console.log(a, b, c, d);
Out[1]:
Default values are calculated on demand.
In [1]:
function getVal1() {
console.log('getVal1');
return 'a';
}
function getVal2() {
console.log('getVal2');
return 'b';
}
const {val1 = getVal1(), val2 = getVal2()} = { val1: 42 };
console.log(val1, val2);
Out[1]:
In [2]:
const obj = { a: { x: 42, y: 89, z: 123 }, b: { g: 'a', h: 'b', i: 'c' } };
const { a: { x, y, z }, b: { h } } = obj;
console.log(x, y, z, h);
Out[2]:
This works with default values, too.
In [1]:
const obj = { a: { x: 42, y: 89, z: 123 }, b: { g: 'a', h: 'b', i: 'c' } };
const { a: { x, y, z }, b: { h = 'a', j = 'x' } } = obj;
console.log(x, y, z, h, j);
Out[1]:
Watch out if you mix default values with nested objects. If you provide an object as default value it is not merged with the actual extracted object.
In [1]:
const obj = { a: { x: 42, y: 89, z: 123 }, b: { g: 'a', h: 'b', i: 'c' } };
const { a: { x, y, z }, c: { h, j } = { h: 1, j: 2 } } = obj;
console.log(x, y, z, h, j);
Out[1]:
In [1]:
const obj = { a: { x: 42, y: 89, z: 123 }, b: { g: 'a', h: 'b', i: 'c' } };
const { a: { x, y, z }, b: { h, j } = { h: 1, j: 2 } } = obj;
console.log(x, y, z, h, j);
Out[1]:
In [2]:
let iterable = [1, 2, 3, 4];
let [a, b, c, d] = iterable;
console.log(a, b, c, d);
Out[2]:
You don't have to extract all values.
In [1]:
let iterable = [1, 2, 3, 4];
let [a, b] = iterable;
console.log(a, b);
Out[1]:
The right-hand side must be an iterable. The following examples throw a TypeError:
In [2]:
let [x] = {}; // An empty object is not iterable
let [y] = undefined;
let [z] = null;
In [1]:
let iterable = [1, 2, 3, 4];
let [a, ...r] = iterable;
console.log(a, r);
Out[1]:
In [1]:
let iterable = [1, 2, 3, 4];
let [,,c, ...r] = iterable;
console.log(c, r);
Out[1]:
In [1]:
// 1. Variable declarations
var { prop1, prop2 } = { prop1: 1, prop2: 2 };
let [a, b] = [1, 2];
const { x, y } = { x: 42, y: 84 };
console.log('Variable declarations:');
console.log(prop1, prop2, a, b, x, y);
// 2. Assignments
({ a, b, x1 = 12, y1 = 24 } = { a: prop1, y1: x });
[ g, ...h ] = [42];
console.log('Assignments:');
console.log(a, b, x1, y1, g, h);
// 3. Parameter definitions
function printCoords ({ x, y, z = 0 }) {
console.log(x, y, z);
}
console.log('Parameter definitions:');
printCoords({ x: 4, y: 2 });
Out[1]:
Note that a statement cannot start with a curly brace ({
). If you use object destructuring in an assignment you'll probably have to wrap the statement in parentheses.
You don't have to use just a simple variable name as assignment target. It could also be an array element or an object property.
In [1]:
const arr = [], obj = {};
({ prop: arr[0] } = { prop: '42' });
[ obj.head, ...obj.tail ] = [1, 2, 3, 4];
console.log(arr);
console.log(obj);
Out[1]:
In [2]:
const RED1 = Symbol('red');
const RED2 = Symbol('red');
console.log(RED1 === RED2, RED1 === RED1, RED2 === RED2);
console.log(RED1 == RED2, RED1 == RED1, RED2 == RED2);
Out[2]:
Some properties of symbols:
Symbol
) is unique.While a symbol is not automatically coerced to other types, you can convert it explicitly to strings or booleans.
In [1]:
const RED = Symbol('red');
console.log(String(RED));
console.log(RED.toString());
console.log(Boolean(RED));
console.log(!RED);
Out[1]:
As you can see, when you negate a symbol with !
, the symbol is implicitly converted. Coercion does not work in other cases.
But what can you actually do with that new type?
You can use symbols for constants which represent concepts. Without symbols you would probably use string or integer constants.
In [1]:
const RED = Symbol('red');
const GREEN = Symbol('green');
const BLUE = Symbol('blue');
Out[1]:
In [1]:
const _private = Symbol('private property');
const obj = {
data: 'this is the data',
[_private]() {
console.log('private method', this.data);
}
}
obj[_private]();
console.log(JSON.stringify(obj));
console.log(Object.keys(obj));
console.log(Object.getOwnPropertyNames(obj));
console.log(Object.getOwnPropertySymbols(obj));
console.log(Reflect.ownKeys(obj));
Out[1]:
As you can see, you can get access to the symbol via Object.getOwnPropertySymbols()
and Reflect.ownKeys()
. Symbols do not protect you from unauthorized access.
We will see in the article about iterators that ECMAScript 2015 defines the symbol Symbol.iterator
. You can use it for an iterator method of the object.
In [1]:
const square = x => x * x;
const square2 = function(x) { return x * x };
console.log(square(4));
console.log(square2(2));
Out[1]:
An arrow function is more concise than the old way (square2
). You don't have to use the function
keyword and you need to write the return
statement for returning a function result.
Arrow functions return the result of the last expression automatically. You write an arrow (=>
) between the parameter list and the function body. If the function has more than one arguments, you need to enclose the parameter list in parentheses.
In [1]:
const add = (a, b) => a + b;
console.log(add(4, 5));
Out[1]:
In [5]:
function Adder(summand) {
this.summand = summand;
}
Adder.prototype.add = function(numbers) {
return numbers.map(function(n) {
return this.summand + n;
});
};
var add2 = new Adder(2);
console.log(add2.add([1, 2, 3, 4]));
Out[5]:
That is not the result we would expect. The problem is that this
does not point to correct object. The value of this
depends in JavaScript on the value of this
in the scope of the calling function. The function which we pass to map
is called in the map function. this
points to the numbers array in that scope. summand
does not exist there, which is why the result is Nan
for each sum.
How can we fix this?
A solution which is often found in ECMAScript 5 code is to store the correct value of this
in an variable which can be accessed from the function via a closure.
In [7]:
function Adder(summand) {
this.summand = summand;
}
Adder.prototype.add = function(numbers) {
var that = this;
return numbers.map(function(n) {
return that.summand + n;
});
};
var add2 = new Adder(2);
console.log(add2.add([1, 2, 3, 4]));
Out[7]:
Now the solution works as expected.
Another soluton is to bind the value of this
via the bind
method.
In [8]:
function Adder(summand) {
this.summand = summand;
}
Adder.prototype.add = function(numbers) {
return numbers.map(function(n) {
return this.summand + n;
}.bind(this));
};
var add2 = new Adder(2);
console.log(add2.add([1, 2, 3, 4]));
Out[8]:
That works, too.
And a third solution for map
is passing the value of this
as a second argument.
In [9]:
function Adder(summand) {
this.summand = summand;
}
Adder.prototype.add = function(numbers) {
return numbers.map(function(n) {
return this.summand + n;
}, this);
};
var add2 = new Adder(2);
console.log(add2.add([1, 2, 3, 4]));
Out[9]:
Arrow functions in ECMAScript 2015 bind the value of this
automatically to the correct value.
In [12]:
function Adder(summand) {
this.summand = summand;
}
Adder.prototype.add = function(numbers) {
return numbers.map(n => {
return this.summand + n
});
};
var add2 = new Adder(2);
console.log(add2.add([1, 2, 3, 4]));
Out[12]:
If we use all ECMAScript 2015 features in our class, the code looks like this:
In [1]:
class Adder {
constructor(summand) {
this.summand = summand;
}
add(numbers) {
return numbers.map(n => this.summand + n);
}
}
const add4 = new Adder(4);
console.log(add4.add([1, 2, 3, 4]));
Out[1]:
By the way, you could also use the following method to achieve the same result as we did with the class before.
In [2]:
const adder = a => b => a + b;
const add5 = adder(5);
[1, 2, 3, 4].map(add5);
Out[2]:
The function adder
accepts one argument and returns a function which accepts another argument.
argument
in ECMAScript 5In JavaScript exists the special variable arguments
which you can use to access all arguments passed to the function. It looks like an array, but it isn't. It contains an entry for each argument passed to the function. The length
property contains the number of arguments passed to the function and hence the number of entries in arguments
.
In [6]:
function log() {
for (var i = 0; i < arguments.length; i++) {
console.log(i, arguments[i]);
}
}
log('a', 'b', 'c');
Out[6]:
But it isn't an array which means that you cannot use the Array
functions for accessing function parameters.
In [9]:
function log() {
// this throws a TypeError: "arguments.forEach is not a function"
arguments.forEach(function (item, index) {
console.log(index, item);
});
}
log('a', 'b', 'c');
You can use the slice
method to convert arguments
to an array and access the function arguments like an array.
In [11]:
function log() {
var args = [].slice.call(arguments);
// or:
// var args = Array.prototype.slice.call(arguments);
args.forEach(function(item, index) {
console.log(index, item);
});
}
log('a', 'b', 'c');
Out[11]:
ECMAScript 2015 introduces the rest operator (...
). You can apply it before argument which should get all remaining function arguments.
In [13]:
function log(...args) {
args.forEach((item, index) => {
console.log(index, item);
});
}
log('a', 'b', 'c');
Out[13]:
The rest parameter must be the last one and it collects all arguments which aren't assigned to the arguments before.
In [21]:
function log(message, ...args) {
console.log(message);
args.forEach(item => {
console.log(' -', item);
});
}
log('The numbers from 1 to 5:', 1, 2, 3, 4, 5);
Out[21]:
In [22]:
function add(a, b, c) {
return a + b + c;
}
const args = [1, 2, 3];
console.log(add(...args));
Out[22]:
You can use the spread operator also if you want to insert the elements of an array into a new array.
In [1]:
const arr1 = [4, 5, 6, 7];
const arr2 = [1, 2, 3, ...arr1, 8, 9, 10];
console.log(arr2);
Out[1]:
In [20]:
function point(x=0, y=0, z=0) {
return {x: x, y: y, z: z};
}
console.log(point());
console.log(point(4, 2));
console.log(point(8, 5, 2));
Out[20]:
In [2]:
function print({text, appendNewLine=false, indentLevel=0}) {
console.log(text, appendNewLine, indentLevel);
}
print({text: 'This is the text.', indentLevel: 8});
Out[2]:
In [2]:
function doSomething({condition1=true, condition2=true}) {
let succeeded = condition1 && condition2;
let result = Math.random();
return {succeeded: succeeded, result: result};
}
let {succeeded, result} = doSomething({condition1: true, condition2: false});
if (succeeded) {
console.log(`Result: ${result}!`);
} else {
console.log('Failed!');
}
({succeeded, result} = doSomething({condition1: true, condition2: true}));
if (succeeded) {
console.log(`Result: ${result}!`);
} else {
console.log('Failed!');
}
Out[2]:
this
when passing a function as parameter to another function (higher order function).arguments
special variable obsolete. It collects all remaining parameters and assigns them to an array.
In [ ]: